#include "MatrixMultiply.h"
#include "Matrix.h"
#include <climits>
#include <ctime>
#include <cfloat>
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <omp.h>
#include <math.h>

#include "naiv.h"
#include "ddot.h"
#include "daxpy.h"
#include "dgemv.h"
#include "dgemm.h"

using namespace std;

typedef void (MatrixMultiply::*MatrixMultiplyFunc)();

extern unsigned int matrices;

double measure(const int iter, MatrixMultiply& mm, MatrixMultiplyFunc func, const int maxRep = 3);
const int iterFromModel(const double time);

int main(int argc, const char* argv[])
{
	int maxCount = 800;
	int step = 16;

	MatrixMultiplyFunc func[5];
	func[0] = &MatrixMultiply::calcNaive;
	func[1] = &MatrixMultiply::cblas_myDdot;
	func[2] = &MatrixMultiply::cblas_myDaxpy;
	func[3] = &MatrixMultiply::cblas_myDGEMV;
	func[4] = &MatrixMultiply::cblas_myDGEMM;

	const char *fileName = "zeitmessung.txt";
	if(argc >= 2)
	{
		fileName = argv[1];
	}
	cout << "writing " << fileName << endl;
	fstream myfile;
	myfile.open(fileName, ios::out | ios::trunc);
	//FileHead
	myfile << "Problemgroesse\tNaiv\tDDOT\tDAXPY\tDGEMV\tDGEMM";

	int iter[5];
	double time;
	for(int n=96; n <= maxCount; n+=step)
	{
		int ld = (n%2) ? n : n+1;
		myfile << endl << n << "\t";
		cout << endl << n << " (ld: " << ld << ")" << "\t";

		Matrix matA(n, ld);
		Matrix matB(n, ld);
		Matrix matC(n, ld);
		MatrixMultiply mm(&matA, &matB, &matC);

		iter[0] = iterFromModel(naiv_model(n));
		iter[1] = iterFromModel(ddot_model(n));
		iter[2] = iterFromModel(daxpy_model(n));
		iter[3] = iterFromModel(dgemv_model(n));
		iter[4] = iterFromModel(dgemm_model(n));
		
		for(int i=0; i<sizeof(iter)/sizeof(iter[0]); ++i)
		{
			time = measure(iter[i], mm, func[i]);
			//time = measure(10000, mm, func[i]);
			myfile << time << "\t";
			cout << time << " (iter: " << iter[i] << ")" << "\t";
		}
	}
	
	myfile.close();
	cout << endl << "Done with the Measuring, press Enter to Exit" << endl;
	cin.get();
	return 0;
}

double measure(const int iter, MatrixMultiply& mm, MatrixMultiplyFunc func, const int maxRep)
{
	double time = (double)FLT_MAX;
	double diter = (double)iter;
	for(int rep = 0; rep < maxRep; rep++)
	{
		double start = omp_get_wtime();
		for(int its=0; its < iter; its++)
		{
			(mm.*func)();
		}
		double stop = omp_get_wtime();
		double t = (stop-start)/iter;
		time = min(time, t);
	}
	return time;
}

const int iterFromModel(const double time)
{
	const double minTime = 0.02;
	return min(1000, max(1, (int)ceil(minTime/time)));
}
